package com.bupt.utils.excel;

import com.bupt.annotation.MParam;
import com.bupt.base.BaseDataSupport;
import com.bupt.exception.BaseException;
import com.bupt.utils.ObjectsUtils;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.apache.poi.hssf.usermodel.*;
import org.apache.poi.ss.usermodel.*;
import org.apache.poi.ss.util.CellRangeAddressList;
import org.springframework.lang.NonNull;

import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.math.BigDecimal;
import java.nio.charset.StandardCharsets;
import java.text.SimpleDateFormat;
import java.util.*;
import java.util.function.Function;

/**
 * EXCEL 相关 操作
 * 主要实现的是 EXCEL 的导出 以及 解析的操作
 *
 * @author breakpoint/赵先生
 * create on 2020/10/28
 */
@Slf4j
public final class ExcelUtils {
    private static final Short FONT_SIZE = 14; //字体的大小
    private static final String FONT_NAME = "楷体"; // 字体名字
    private static final Short LINE_HEIGHT = 20; // 行高
    private static final Short MIN_SELECT_ROW_NUM = 200; // 最小的下拉的数量

    private ExcelUtils() {/*refuse new obj*/}

    // 生成多页的信息 key: sheetName val: message
    public static <T> HSSFWorkbook getHSSFWorkbook(Map<String, List<T>> dataMapVal,
                                                   @MParam("是否下载模版") boolean isMould) throws BaseException {
        if (null == dataMapVal || dataMapVal.size() == 0) throw new BaseException("没有数据，无法导出");
        HSSFWorkbook workbook = new HSSFWorkbook();//这里也可以设置sheet的Name
        // 设置单元格的格式
        HSSFFont font = workbook.createFont();
        // 获取到单元的样式
        HSSFCellStyle cellStyle = workbook.createCellStyle();
        // 设置数据的格式
        HSSFDataFormat dataFormat = workbook.createDataFormat();
        cellStyle.setDataFormat(dataFormat.getFormat("General"));
        font.setFontName(FONT_NAME);
        // 设置大小
        font.setFontHeightInPoints(FONT_SIZE);
        // 设置格式的基本信息
        cellStyle.setFont(font);
        // 设置对齐的格式
        cellStyle.setVerticalAlignment(VerticalAlignment.CENTER);
        cellStyle.setAlignment(HorizontalAlignment.CENTER);
        // 循环的输出数据
        for (Map.Entry<String, List<T>> entry : dataMapVal.entrySet()) {
            List<T> data = entry.getValue();
            HSSFSheet sheet = workbook.createSheet(entry.getKey());
            // 设置 val
            setSheetVal(sheet, data, isMould, cellStyle, font);
        } // end for Map.Entry<String, List<T>> entry : data.entrySet()
        return workbook;
    }

    // 获取到 HSSFWorkbook 下载我们的数据
    public static <T> HSSFWorkbook getHSSFWorkbook(String sheetName, List<T> data,
                                                   @MParam("是否下载模版") boolean isMould) throws BaseException {
        if (null == data || data.size() == 0) throw new BaseException("没有数据，无法得到 HSSFWorkbook");
        if (null == sheetName || "".equals(sheetName)) throw new BaseException("sheetName is empty");

        HSSFWorkbook workbook = new HSSFWorkbook();//这里也可以设置sheet的Name
        //创建工作表对象
        HSSFSheet sheet = workbook.createSheet(sheetName);
        sheet.setDefaultRowHeightInPoints(LINE_HEIGHT);
        sheet.setDefaultColumnWidth(LINE_HEIGHT);
        // 设置单元格的格式
        HSSFFont font = workbook.createFont();
        // 获取到单元的样式
        HSSFCellStyle cellStyle = workbook.createCellStyle();
        // 设置数据的格式
        HSSFDataFormat dataFormat = workbook.createDataFormat();
        cellStyle.setDataFormat(dataFormat.getFormat("General"));
        font.setFontName("楷体");
        // 设置大小
        font.setFontHeightInPoints(FONT_SIZE);
        // 设置格式的基本信息
        cellStyle.setFont(font);
        // 设置对齐的格式
        cellStyle.setVerticalAlignment(VerticalAlignment.CENTER);
        cellStyle.setAlignment(HorizontalAlignment.CENTER);
        setSheetVal(sheet, data, isMould, cellStyle, font);
        return workbook;
    }

    // 设置 val 给每一个 sheet
    @SuppressWarnings("unchecked")
    private static <T> void setSheetVal(HSSFSheet sheet, List<T> data, @MParam("是否下载模版") boolean isMould,
                                        @MParam("单元格的格式") HSSFCellStyle cellStyle, HSSFFont font) throws BaseException {
        // 设置header的行
        HSSFRow header = sheet.createRow(0);//设置第一行，从零开始 设置 header
        // 设置header的行高
        header.setHeightInPoints(LINE_HEIGHT);
        // 获取到第一列的数据
        Map<String, Object> headerData = getDataMap(data.get(0), true, isMould);
        // 获取到Excel的header的信息
        ArrayList<ExcelField> headerTitle = (ArrayList<ExcelField>) headerData.get(HEADER_KEY);
        // 设置模版的颜色 如果是模版的化话 那么就进行 设置为红色 其他的情况设置为 普通的
        short cellColor = isMould ? Font.COLOR_RED : Font.COLOR_NORMAL;
        // 设置颜色
        font.setColor(cellColor);
        // 获取最大的列宽
        Map<Integer, Integer> maxWith = new HashMap<>();
        // 循环的操作
        for (int i = 0; i < headerTitle.size(); i++) {
            // 设置默认的格式信息
            sheet.setDefaultColumnStyle(i, cellStyle);
            // 设置标题
            HSSFCell cell = header.createCell(i);
            setCellValue(headerTitle.get(i).name(), cell, i, maxWith, headerTitle.get(i));
            // 只有多选以及下载模板的时候 才会有效果的
            if (headerTitle.get(i).selectValues().length > 0 && isMould) {
                // 设置最后的下拉框
                int lastRow = data.size() < MIN_SELECT_ROW_NUM ? MIN_SELECT_ROW_NUM : data.size();
                DataValidationHelper helper = sheet.getDataValidationHelper();
                DataValidationConstraint constraint = helper.createExplicitListConstraint(headerTitle.get(i).selectValues());
                CellRangeAddressList addressList = new CellRangeAddressList(1, lastRow, i, i);
                DataValidation dataValidation = helper.createValidation(constraint, addressList);
                sheet.addValidationData(dataValidation);
            }
        }
        HSSFRow row = sheet.createRow(1);
        row.setHeightInPoints(LINE_HEIGHT);
        for (int i = 0; i < headerTitle.size(); i++) {
            // 获取到值信息
            Object cellData = headerData.get(headerTitle.get(i).name());
            HSSFCell cell = row.createCell(i);
            // 标题一定是正常的颜色的
            setCellValue(cellData, cell, i, maxWith, headerTitle.get(i));
        }
        for (int i = 1; i < data.size(); i++) {
            HSSFRow innerRow = sheet.createRow(i + 1);
            // 设置行高
            innerRow.setHeightInPoints(LINE_HEIGHT);
            Map<String, Object> dataMap = getDataMap(data.get(i), false, isMould);
            for (int j = 0; j < headerTitle.size(); j++) {
                Object cellData = dataMap.get(headerTitle.get(j).name());
                HSSFCell cell = innerRow.createCell(j);
                // 数据需要设置真实的颜色的
                setCellValue(cellData, cell, j, maxWith, headerTitle.get(j));
            }
        }
        // 设置列宽
        for (int i = 0; i < headerTitle.size(); i++) {
            //sheet.autoSizeColumn(i);
            //sheet.setColumnWidth(i, sheet.getColumnWidth(i) * 15 / 10);
            Integer width = maxWith.get(i);
            double k = 0.4 * width / 255;
            if (headerTitle.get(i).isWrapText()) {
                sheet.setColumnWidth(i, (int) (width / 2.0 * (1.5 - k) * 256));
            } else {
                sheet.setColumnWidth(i, (int) (width * (1.5 - k) * 256));
            }
        }
    }

    // 获取一个类里面的所有的注解 获取到所有的属性的信息
    private static @NonNull
    <T> List<Field> getClazzFieldFromClazz(Class<T> clazz) {
        List<Field> res = new ArrayList<Field>();
        Class<? super T> superclass = clazz.getSuperclass();
        if (null != superclass && !superclass.isInterface()) {
            // 将所有的 ExcelField 加入
            res.addAll(getClazzFieldFromClazz(superclass));
        }
        if (!clazz.isInterface()) {
            Field[] declaredFields = clazz.getDeclaredFields();
            // 加入所有的属性
            res.addAll(new ArrayList<>(Arrays.asList(declaredFields)));
        }
        return res;
    }

    // 设置值的操作 设置单元格的格式 以及
    private static void setCellValue(@MParam("数据信息") Object cellData, @MParam("单元格") HSSFCell cell,
                                     int index, Map<Integer, Integer> maxWith, ExcelField excelField) {
        // 目标值
        String targetVal = null;
        if (cellData instanceof Integer) {
            targetVal = String.valueOf((Integer) cellData);
        } else if (cellData instanceof Long) {
            targetVal = String.valueOf((Long) cellData);
        } else if (cellData instanceof Double) {
            // 保留6位有效数字
            //BigDecimal bigDecimal = new BigDecimal((double) Math.round(((Double) cellData) * 1000000) / 1000000);
            targetVal = String.valueOf((double) Math.round(((Double) cellData) * 1000000) / 1000000);
        } else if (cellData instanceof Date) {
            SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
            targetVal = sdf.format((Date) cellData);
        } else if (cellData instanceof String) {
            targetVal = (String) cellData;
            // 校验数据的有效性 防止出现 null 在excel 里面
            if (null == targetVal || Objects.equals("null", targetVal) || Objects.equals("NULL", targetVal)) {
                targetVal = "";
            }
        } else {
            // 其他的情况的时候 我们的操作
            // # issue 出现 null的数据的问题
            if (cellData == null) {
                targetVal = "";
            } else {
                targetVal = String.valueOf(cellData);
            }
        }
        // 设置数值
        if (excelField.isWrapText()) {
            cell.setCellValue(new HSSFRichTextString(targetVal));
        } else {
            cell.setCellValue(targetVal);
        }
        try {
            int max = Math.max(maxWith.getOrDefault(index, 3),
                    targetVal.getBytes(StandardCharsets.UTF_8).length);
            if (max > 150) {
                max = 150;
            }
            maxWith.put(index, max);
        } catch (Exception e) {
            maxWith.put(index, 0);
        }
    }

    // header 的 key
    private static final String HEADER_KEY = "header_key";

    // 得到一个 对象 的map
    private static <T> Map<String, Object> getDataMap(
            @MParam("请求的数据信息") T data,
            @MParam("返回的结果里面是否具有header信息") boolean isAttachHeaderMsg,
            @MParam("是否下载模版") boolean isMould) throws BaseException {
        // 定义返回值
        Map<String, Object> res = null;
        // ExcelField list
        List<ExcelField> tempList = new ArrayList<>();
        if (data != null) {
            res = new TreeMap<>();
            // 获取到实体类的信息
            Class<?> clazz = data.getClass();
            List<Field> declaredFields = getClazzFieldFromClazz(clazz);
            if (declaredFields.size() > 0) {
                for (Field field : declaredFields) {
                    // 获取到注释
                    ExcelField annotation = field.getAnnotation(ExcelField.class);
                    if (null == annotation) continue;
                    String key = field.getName();
                    if ("serialVersionUID".equals(key)) continue;
                    if (!"".equals(annotation.name())) {
                        key = annotation.name();
                    }
                    // 最小堆来进行排序的操作
                    if (annotation.isExplored()) { // 是否是导出列
                        // 操作的逻辑 如果是导出的列 查看当前是否是模版的下载，如果是的话 那么检查当前是痘需要这一列
                        if (!isMould || (isMould && annotation.isMouldColumn())) {
                            tempList.add(annotation);
                        }
                    } else if (isMould && annotation.isMouldColumn()) {
                        // 如果当前不是导出列 但是是模板列 如么就加入其中
                        tempList.add(annotation);
                    }
                    if (res.containsKey(key)) throw new BaseException("具有相同的意思的属性，不满足单一性");
                    try {
                        Method method = clazz.getMethod(getGetMethodName.apply(field.getName()));
                        // 获取操作值的操作
                        Object valObj = method.invoke(data);
                        if (null != valObj) {
                            if (valObj instanceof Date) {
                                // 设置日期的格式
                                String pattern = annotation.datePattern();
                                // 设置简单的日期的格式
                                SimpleDateFormat sdf = new SimpleDateFormat(pattern);
                                // 调用这个方法
                                res.put(key, sdf.format(valObj));
                            } else if (valObj instanceof Boolean || valObj.getClass() == boolean.class) {
                                // 实际保存的对象
                                Object targetVal = ((boolean) valObj) ? "是" : "否";
                                res.put(key, targetVal);
                            } else {
                                res.put(key, valObj);
                            }
                        }//
                    } catch (Exception e) {
                        //throw new BaseException(e.getMessage());
                        // nothing to do
                        continue;
                    }
                }
            }
        }
        // 是否进行保存
        if (res != null && isAttachHeaderMsg) {
            if (!res.containsKey(HEADER_KEY)) res.put(HEADER_KEY, getSortedRes(tempList));
        }
        return res;
    }

    // 采用内部的排序
    private static List<ExcelField> getSortedRes(List<ExcelField> origin) {
        origin.sort(Comparator.comparingInt(ExcelField::order));
        return origin;
    }

    // 获取到方法的名字
    private static final Function<String, String> getGetMethodName = (name) -> "get" + getCapLetter(name);

    // 获取set方法
    private final static Function<String, String> getSetMethodName = (name) -> "set" + getCapLetter(name);

    // 获取首字母大写的 字符串
    private static String getCapLetter(String str) {
        // 获取到第一个字母大写的字符串
        return str.substring(0, 1).toUpperCase() + str.substring(1, str.length());
    }

    /*    解析上传数据的操作    */

    // 获取多个页的数据
    // 解析数据 根据提交上来的文件流信息 解析数据 当然 仅仅可以解析 xsl
    // Map<String, Class<? extends T>> clazzMap  key:sheetName value:clazz msg
    public static <T> Map<String, List<T>> getDataMapFromCurrentInput(
            @MParam("文件流的信息") InputStream inputStream,
            @MParam("类信息") Map<String, Class<? extends T>> clazzMap) throws BaseException {
        try {
            // 获取到工作本
            Workbook sheets = WorkbookFactory.create(inputStream);
            // 设置返回对象
            Map<String, List<T>> res = new HashMap<String, List<T>>();
            // 循环解析操作
            for (Map.Entry<String, Class<? extends T>> entry : clazzMap.entrySet()) {
                Sheet sheet = sheets.getSheet(entry.getKey());
                if (null == sheet) throw new BaseException("没有对应的 sheet 等于" + entry.getKey() + ",请检查上传的文件");
                // 设置返回值 加入每一个页面的数据
                res.put(entry.getKey(), getDataList(sheet, entry.getValue()));
            } //end for Map.Entry<String, Class<? extends T>> entry : clazzMap.entrySet()
            // 返回上传的数据
            return res;
        } catch (IOException e) {
            throw new BaseException("解析上传信息失败 e={" + e.getMessage() + "}");
        }

    }

    // 解析上传的数据 返回一个List 类型的数据 自动的解析   T ---> List<T> will return
    // 解析上传的数据
    public static <T> List<T> getDataList(@MParam("输入流") InputStream inputStream,
                                          @MParam("返回的数据的类型") Class<? extends T> clazz) throws BaseException {
        try {
            // 获取到工作本
            Workbook sheets = WorkbookFactory.create(inputStream);
            // 获取到第一个数据 默认解析第一个
            Sheet sheetAt = sheets.getSheetAt(0);
            // 返回上传的数据
            return getDataList(sheetAt, clazz);
        } catch (IOException e) {
            //e.printStackTrace();
            throw new BaseException("解析上传信息失败 e={" + e.getMessage() + "}");
        }
    }

    // 解析一个页面的数据
    private static <T> List<T> getDataList(Sheet sheet, Class<? extends T> clazz) throws BaseException {
        List<T> res = new ArrayList<T>();
        List<String> fields = null;
        if (null != sheet) {
            for (Row row : sheet) {
                //如果第一行不是标题 那就去掉
                if (row.getRowNum() == 0) {
                    if (null == fields) fields = getMethodList(row);
                    continue;
                }
                try {
                    T data = getDataFromRow(row, clazz, fields);
                    res.add(data);
                } catch (BaseException e) {
                    throw new BaseException(e.getMessage());
                } catch (Exception e1) {
                    //  nothing to do
                    //e1.printStackTrace();
                    log.error("解析数据发生异常 e={}", e1.getMessage());
                }
            }// end for
        } // end if
        return res;
    }// end method

    // 获取数据 根据数据行 来获取
    private static <T> T getDataFromRow(Row row, Class<? extends T> clazz, List<String> fields)
            throws BaseException {
        Map<String, Object> valMap = new HashMap<>();
        for (int i = 0; i < fields.size(); i++) {
            // 设置默认值
            valMap.put(fields.get(i), ObjectsUtils.defaultIfNull(getRealVal(row.getCell(i)), StringUtils.EMPTY));
        }
        try {
            // 获取构造函数 默认获取无参数的构造方法
            Constructor<? extends T> constructor = clazz.getConstructor();
            // 创建一个新的返回对象
            T res = constructor.newInstance();
            // 获取到所有的属性
            List<Field> declaredFields = getClazzFieldFromClazz(clazz);
            // 设置我们属性值
            if (null != declaredFields && declaredFields.size() > 0) {
                for (Field field : declaredFields) {
                    // 获取到注释
                    ExcelField annotation = field.getAnnotation(ExcelField.class);
                    // 没有注解的 那么就 进行 其他属性的操作
                    if (annotation == null) continue;
                    // 如果不是模板列 那么就不解析这一个属性
                    if (!annotation.isMouldColumn()) continue;
                    String key = field.getName();
                    if ("serialVersionUID".equals(key)) continue;
                    if (!"".equals(annotation.name())) {
                        key = annotation.name();
                    }
                    // 获取到设置值的方法名
                    String setMethodName = getSetMethodName.apply(field.getName());
                    // 参数的类型 也是属性的类型
                    Class<?> type = field.getType();
                    // 获取到方法
                    Method method = clazz.getMethod(setMethodName, type);
                    // 下面的是利用Java的反射来设置值
                    if (type == Integer.class || type == int.class) {
                        // 获取目标的值的操作
                        int intVal;
                        Object targetVale = valMap.get(key);
                        if (targetVale instanceof Double) {
                            intVal = ((Double) targetVale).intValue();
                        } else {
                            intVal = (Double.valueOf((String) targetVale)).intValue();
                        }
                        method.invoke(res, intVal);
                        continue;
                    }
                    if (type == Long.class || type == long.class) {
                        long longVal;
                        Object targetVale = valMap.get(key);
                        if (targetVale instanceof Double) {
                            longVal = ((Double) targetVale).longValue();
                        } else {
                            longVal = (Double.valueOf((String) targetVale)).longValue();
                        }
                        method.invoke(res, longVal);
                        continue;
                    }
                    if (type == Double.class || type == double.class) {
                        double doubleVal;
                        Object targetVale = valMap.get(key);
                        if (targetVale instanceof Double) {
                            doubleVal = (Double) targetVale;
                        } else {
                            doubleVal = Double.valueOf((String) targetVale);
                        }
                        method.invoke(res, doubleVal);
                        continue;
                    }
                    if (type == String.class) {
                        String targetVal = null;
                        // 暂时仅仅string的数据需要进行校验的操作
                        if (annotation.selectValues().length > 0) {
                            // 获取到目标的值
                            targetVal = (String) valMap.get(key);
                            if (!BaseDataSupport.valContainsInArr(targetVal, annotation.selectValues())) {
                                throw new BaseException("请下拉选择excel表中的" + key + "列的数据");
                            }
                        }
                        // 解决科学记数法的问题
                        if (valMap.get(key) instanceof Double) {
                            BigDecimal bigDecimal = new BigDecimal(((Double) valMap.get(key)));
                            targetVal = bigDecimal.toPlainString();
                        } else {
                            targetVal = (String) valMap.get(key);
                        }
                        method.invoke(res, targetVal);
                        continue;
                    }
                    if (type == Date.class) {
                        // 日期的类型的比较特殊
                        // 解析日期数据
                        Object val = valMap.get(key);
                        if (val instanceof Date) {
                            method.invoke(res, val);
                        } else {
                            method.invoke(res, BaseDataSupport.parseDateStr((String) val));
                        }
                        continue;
                    }
                }
            }
            return res;
        } catch (NoSuchMethodException e) {
            throw new BaseException("请保留无参数的构造方法");
        } catch (InstantiationException | IllegalAccessException
                | IllegalArgumentException | InvocationTargetException e2) {
            e2.printStackTrace();
            throw new BaseException("创建新的对象失败");
        }// end
    }// end method

    // 获取相应的 excel 的第一行的数据
    // 也就是解析第一行的标题 为以后的 数据映射作准备
    private static <T> List<String> getMethodList(Row row) throws BaseException {
        short lastCellNum = row.getLastCellNum();
        List<String> res = new ArrayList<>();
        for (int i = 0; i < (int) lastCellNum; i++) {
            String key = row.getCell(i).getStringCellValue();
            // 仅仅取到前面的数据
            if (null == key || "".equals(key)) break;
            if (res.contains(key)) throw new BaseException("具有相同的意思的属性，不满足单一性");
            res.add(key);
        }
        return res;
    }

    // 获取真实的值
    @SuppressWarnings({"deprecation"})
    private static Object getRealVal(Cell cell) {
        if (cell.getCellType().getCode() == 1) {
            //  STRING(1),
            return cell.getStringCellValue();
        } else if (cell.getCellType().getCode() == 0) {
            // NUMERIC(0),
            if (!"General".equals(cell.getCellStyle().getDataFormatString())) {
                return cell.getDateCellValue();
            }
            return cell.getNumericCellValue();
        } else if (cell.getCellType().getCode() == 4) {
            // BOOLEAN(4),
            return cell.getBooleanCellValue();
        } else {
            // other is null
            return null;
        }
    }
}
